#include <dlfcn.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>

#include "base/Simulator.h"
#include "base/Configuration.h"
#include "base/StatisticsCollection.h"
#include "base/simapi.h"

static void sim_atexit();

char* padding_dlopen_before; // see comment in makeSimulator()
char* padding_dlopen_after;
Simulator* makeSimulator (int argc, const char** argv, const char** envp);


#ifdef WITH_PIN_SUPPORT
// jw : the following code is to simulator app code without source so that
// we can't change app main() to mainX(). i commented it out since
// i don't have a nice idea on how to integrate it cleanly to our mainX-based
// bootstrap code.

extern "C" {
int __wrap_main (int argc, const char** argv, const char** envp);
}

int __wrap_main (int argc, const char** argv, const char** envp) {
#else
int main (int argc, const char** argv, const char** envp) {
#endif // WITH_PIN_SUPPORT
    int orig_argc = argc;
    global_cfg = NEW Configuration(&argc, argv, envp);
    Log = NEW Logger("log", "g", "-:w,log.sim", "main log");
    nullLog = Log;
    nullLog = NEW Logger("null", "", "none", "null log", false);

    for (const char** env = envp; *env != NULL; env++) {
        Log->i("export %s\n", *env);
    }
    for (int i = 0; i < orig_argc; i++) {
        Log->i("argv[%d]=%s\n", i, argv[i]);
    }

    // bdc: automatically "nice" the simulator so people running batch
    // simulations don't disturb interactive users. Note the value of
    // 10 is not the lowest priority possible. This is so that
    // test-less related scripts can run at the even lower priority of 19.
    int priority = global_cfg->getIntegerArgument("priority",
                                                  "set OS scheduling priority (aka nice level)",
                                                  10, CONFIG_RANGE, -20, 19);
    setpriority(PRIO_PROCESS, getpid(), priority);

    // if time_limit is set, limit simulator CPU time usage to prevent
    // runaway simulations, especially for automated regression
    int time_limit = global_cfg->getIntegerArgument("time_limit",
                                                    "CPU time limit (seconds), 0 means disabled",
                                                    0, CONFIG_UNSIGNED);
    if (time_limit) {
        struct rlimit rlim;
        rlim.rlim_cur = time_limit;
        // give 60 seconds between soft limit and hard limit
        rlim.rlim_max = time_limit+60;
        int results = setrlimit(RLIMIT_CPU, &rlim);
        ASSERT(results == 0);
    }

    // if fsize_limit is set, limit simulator output file size to prevent
    // runaway simulations, especially for automated regression on cluster machines
    int fsize_limit = global_cfg->getIntegerArgument("fsize_limit",
                                                     "File size limit (bytes), 0 means disabled",
                                                     0, CONFIG_UNSIGNED);
    if (fsize_limit) {
        struct rlimit rlim;
        rlim.rlim_cur = fsize_limit;
        // give an extra megabyte between soft limit and hard limit
        rlim.rlim_max = fsize_limit+(1*1024*1024);
        int results = setrlimit(RLIMIT_FSIZE, &rlim);
        ASSERT(results == 0);
    }
    
    // if core_limit is set, allow creation of simulator core dumps
    int core_limit = global_cfg->getIntegerArgument("core_limit",
                                                    "Core file size limit (bytes), 0 means disabled",
                                                    0, CONFIG_UNSIGNED);
    if (core_limit) {
        struct rlimit rlim;
        rlim.rlim_cur = core_limit;
        // give an extra megabyte between soft limit and hard limit
        rlim.rlim_max = fsize_limit+(1*1024*1024);
        int results = setrlimit(RLIMIT_CORE, &rlim);
        ASSERT(results == 0);
    }

    bool verbose = global_cfg->getBooleanArgument("verbose",
                                                  "Display all configured options");

    // useful for when you need to use "gdb --pid" to attach
    if (verbose) {
        printf("pid=%d\n", getpid());
    }

    // useful for programmatic termination (such as via robot shutdown)
    FILE* pid;
    {
        char _tmp[2048];
        Logger::getOutputPath(_tmp, "pid");
        pid = fopen(_tmp, "w");
        ASSERT(pid);
    }
    fprintf(pid, "%d\n", getpid());
    fclose(pid);

    // register atexit handler to trap problem of exit without sys_goto_less.
    int result = atexit(sim_atexit);
    ASSERT(result == 0);
    
    global_sim = makeSimulator(argc, argv, envp);
    global_cfg->checkUnused();

    // print configuration, argv, and envp to top of main log file to
    // make it easier to reproduce problems
    global_cfg->printArgs(Log, verbose ? SEV_WARNING : SEV_INFO);

    int exitCode = 1;
    if (global_sim != NULL) {
        global_sim->run();
        // IMMEDIATELY after the run completes, print simclock in case we crash in the cleanup code
        Log->i("Execution completed successfully!\n");
        Log->i("simclock: %lli\n", global_sim->getSimclock());
        Log->i("simclockStats: %lli\n", global_sim->getSimclockStats());

        global_sim->printStatistics();

        exitCode = global_sim->getExitCode();

        // NULL after delete to catch unintended usage in Pool:cleanupPools...
        delete global_sim;
        global_sim = NULL;
    }

    delete global_cfg;
    global_cfg = NULL;
    delete Log;
    Log = NULL;
    delete nullLog;
    nullLog = NULL;

    // this dumps pool statistics to Log, so it needs to be after deletes
    Pool::cleanupPools();

    // see comment in makeSimulator()
    free(padding_dlopen_before);
    free(padding_dlopen_after);

    return exitCode;
}

Simulator* makeSimulator (int argc, const char** argv, const char** envp) {
    const char* system = global_cfg->getStringArgument("system", "The system configuration", "scc");

    char makeFuncName[50];
    strcpy(makeFuncName, "makeSimulator_");
    strcat(makeFuncName, system);
    
    /* 
       dlopen is used to lookup mainX from the simulator. It mallocs
       memory for this purpose. When the simulated application also
       uses dlopen, it also accesses this memory. The problem is the
       next memory around this dlopen malloced area is used by the
       simulator. Java applications where getting lots of "L2
       consistency" errors because when they called dlopen other
       memory used by the simulator was changing within their cache
       line, namely the memory allocated immediately after the dlopen
       call which was "P->outstandingMemRequest->data = new
       uint32;". This padding before and after dlopen is to try and
       make sure that simulator data structures are not within the
       same cacheline as the dlopen malloc'd data.  Note that we thus
       use malloc and not CALLOC, since that is what dlopen uses.
     */
    padding_dlopen_before = (char*)malloc(4096);
    memset(padding_dlopen_before, 0xFE, 4096);
    void* handle = dlopen(NULL, RTLD_LAZY|RTLD_GLOBAL);
    ASSERT(handle != NULL);
    ASSERT(dlerror() == NULL);
    void* mainX  = dlsym(handle, "mainX");
    if (mainX == NULL) {
        Log->e("Problem finding mainX %s", dlerror());
    }
    void* makeFunc = dlsym(handle, makeFuncName);
    if (makeFunc == NULL) {
        Log->e("I don't know how to make a %s system (%s)", system, dlerror());
    }
    dlclose(handle);
    padding_dlopen_after = (char*)malloc(4096);
    memset(padding_dlopen_after, 0xFE, 4096);

    Simulator* sim = (*(Simulator* (*)())makeFunc)();
    sim->buildSystem(mainX, argc, argv, envp);
    return sim;
}

// atexit handler to trap calls to exit without a sys_goto_less.
// without this statistics file will not be create.
void sim_atexit () {
    if (!inSimulation) {
        fflush(NULL);
        fprintf(stderr, "caught exit without goto_sim\n");
        goto_sim();
    }
}
